9. Använda filer

 

 

Efter detta kapitel ska eleven känna till:

 

·     Datatypen FILE.

 

·     Öppna och stänga filer.

 

·     Läsa filer.

 

·     Skriva filer.

 

·     Positionering.

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Datatypen FILE.        

 

För att man ska kunna arbeta med filer behöver man definiera en ny struktur, d.v.s. en ny datatyp. Denna datatyp (struktur) finns redan deklarerad i stdio.h och den heter FILE. Tittar man i stdio.h finner man bland annat följande:

 

/* buffered I/O macros */

 

#define BUFSIZ  512

#define _NFILE  40

#define EOF (-1)

 

struct _iobuf {

    char *_ptr;

    int   _cnt;

    char *_base;

    char  _flag;

    char  _file;

    };

typedef struct _iobuf FILE;

 

Här har man skapat en ny typ kallad _iobuf, och sedan givit den ett nytt namn, FILE. Skapar man en egen variabel av denna typ kommer den att innehålla de fem variabler vi ser ovan. De funktioner som vi använder kommer att behöva dessa variabler.

 

Dessutom fick vi tre stycken textkonstanter. En av dem, EOF kommer vi att ha mycket nytta av. Anledningen till att EOF definieras så här är den att olika datorsystem kan ha olika värden för just EOF, men vi behöver inte veta vilket, här definieras den, så vi kan bara skriva EOF. Om du inte redan gissa det så står EOF för End Of File, d.v.s. filslut. Om du har gissat det så står det i alla fall för filslut. Man kan t.ex. testa i en läsloop på följande sätt:

 

while ((cTecken = getc(Infil)) != EOF)

 

Def finns många definitioner, varav vi får begränsa oss till att titta på några och se vad de kan ge oss. En del används endast av de funktioner vi använder.

 

/* fseek constants */

 

#define SEEK_CUR 1

#define SEEK_END 2

#define SEEK_SET 0

#define FILENAME_MAX 128

#define FOPEN_MAX 18

#define TMP_MAX 32767

#define _SYS_OPEN 20

 


Därefter tittar vi på några av de funktioner vi har tillgång till (lugn bara, vi ska inte lära oss alla nu):

 

/* function prototypes */

 

int __cdecl fclose(FILE *);

int __cdecl _fcloseall(void);

int __cdecl feof(FILE *);

int __cdecl ferror(FILE *);

int __cdecl fgetc(FILE *);

int __cdecl fgetpos(FILE *, fpos_t *);

char * __cdecl fgets(char *, int, FILE *);

int __cdecl _fileno(FILE *);

int __cdecl _flushall(void);

FILE * __cdecl fopen(const char *, const char *);

int __cdecl fprintf(FILE *, const char *, ...);

int __cdecl fputc(int, FILE *);

int __cdecl fputs(const char *, FILE *);

size_t __cdecl fread(void *, size_t, size_t, FILE *);

FILE * __cdecl freopen(const char *, const char *, FILE *);

int __cdecl fsetpos(FILE *, const fpos_t *);

int __cdecl fseek(FILE *, long, int);

long __cdecl ftell(FILE *);

size_t __cdecl fwrite(const void *, size_t, size_t, FILE *);

int __cdecl getc(FILE *);

int __cdecl _getw(FILE *);

int __cdecl remove(const char *);

int __cdecl rename(const char *, const char *);

void __cdecl rewind(FILE *);

int __cdecl _rmtmp(void);

void __cdecl setbuf(FILE *, char *);

int __cdecl setvbuf(FILE *, char *, int, size_t);

char * __cdecl _tempnam(char *, char *);

FILE * __cdecl tmpfile(void);

char * __cdecl tmpnam(char *);

int __cdecl ungetc(int, FILE *);

int __cdecl vfprintf(FILE *, const char *, va_list);

 

Vi kommer att titta närmre på en del av dessa funktioner nedan.


Öppna och stänga filer.

 

Man måste be operativsystemet att etablera kontakt med en fil, tala om vad man vill göra med den (läsa/skriva/förlänga), och tala om var i minnet data ska buffras etc. Operativsystemet kontrollerar även att man inte låter flera program samtidigt manipulerar samma fil. Därför är det viktigr att man meddelar operativsystemet när man är klar med den. Dessa två processer kallas att öppna repektive stänga filer.

 

Man öppnar en fil med följande syntax:

 

<FILE-variabel> = fopen(”<filnamn>”,”<arbetsläge>”);

 

FILE-variabel är alltså en pekarvariabel av typen FILE. När vi anropar fopen() kommer funktionen att ge oss en adress till filbuffer etc. som ingår i strukturen FILE. Om DOS av någon anledning inte kan öppna filen kommer vi att få värdet noll i pekaren. Detta måste kollas innan man försöker gå vidare.

 

Filnamn är ett filnamn enligt DOS-format.

 

Arbetsläge kan vara:     r = read, läs fil.

                                   w = write, skriv fil.

                                   a = append, förläng fil.

 

Öppnar man en fil för att läsa kommer filpekaren att stå i början av filen. (Filpekaren gömmer sig i den strukturvariabel du deklarerat av typen FILE.) När du ber om nästa tecken kommer filpekaren att stegas framåt ett tecken i taget tills du hittar EOF.

 

Öppnar man en fil för att skriva kommer filpekaren också att stå i början av filen. När man sedan stänger filen kommer EOF att skrivas där filpekaren står. Då har man antingen skrivit över filens innehåll, eller flyttat EOF till filens början, och DOS kommer att säga att den är tom. Hur du än gör så förlorar du innehållet i filen. Finns inte filen kommer DOS att skapa den.

 

Vill du öppna en fil för skrivning utan att förlora innehållet ska du använda förlängning (append). Då sätts filpekaren vid EOF. Du kan då lägga till data i slutet av filen.

 

Exempel på filöppning:

 

infil = fopen(”c:\\katalog\\filnamn.txt”,”r”)


Syntaxen för att stänga en fil:

 

fclose(<FILE-variabel>);

 

Man behöver alltså bara ange pekaren, inte filnamnet, för att stänga filen.

Exempel:

 

fclose(infil);

 

Observera att fopen() levererade en adress i variabeln infil. Skulle DOS ha något att invända mot att vi öppnar filen får vi ingen adress, vi får värdet noll i stället. Det är alltså högst rekommendabelt att man testar pekaren innan man försöker läsa eller skriva:

 

if (infil == 0)

{

   puts(”Det gick inte att öppna filen!”);

}

else

{

   .

   . // gör vad du ska göra med filen.

   .

}

 

Man kan kombinera ihop funktionsanropet och testen (sånt här älskar äkta C-programmerare, koden blir ännu svårare att förstå):

 

 

if ((infil = fopen(”c:\\katalog\\filnamn.txt”,”r”)) == 0)

{

   puts(”Det gick inte att öppna filen!”);

}

else

{

   .

   . // gör vad du ska göra med filen.

   .

}

 

Och det du ska göra är naturligtvis att läsa eller skriva.


Läsa filer.

 

Ovanstående exempel handlar alla om sekvensfiler. En sekvensfil kan betraktas som en lång rad tecken som avslutats med ett filslutstecken (EOF, -1) vilka skrivits på en disk eller en diskett.

 

Vi kan läsa en fil m.h.a. funktionen fgetc(). Som du kan se ovan finns den deklarerad i stdio.h, och den vill ha en pekarvariabel av type FILE som parameter.

 

Exempel, öppna en fil och läs första bokstaven, visa bokstaven på skärmen och stäng filen:

 

#include <stdio.h>

void main()

{

   FILE *infil;

   char cBokstav;

   if((infil = fopen("test.txt","r")) != 0)

   {

      cBokstav = fgetc(infil);

      printf("Första bokstaven i filen är: %c. \n", cBokstav);

      fclose(infil);

   }

   else puts("Det gick inte att öppna filen!");

}

 

Om då filen test.txt, vilken ligger i samma katalog som programmet, innehåller texten ”Hubba-hopp!”, kommer utskriften att bli:

 

                   

 


Övningsuppgift:

·      Gör en textfil som heter test.txt m.h.a. Workbench editor, och mata in texten: ”Denna fil har jag skrivit själv.
Nu ska jag göra ett program som kan läsa den och visa på skärmen.”

·      Gör ett program lasfil som läser filen och skriver ut på skärmen.

·      Utskriften ska se ut som nedan:

 

     

 

Övningsuppgift:

·      Gör ett program som räknar antalet tecken i textfilen från exemplet ovan.

·      Kalla programmet ccnt.

 

Överkursuppgift:

·      Gör ett program som räknar orden i samma textfil.

·      Kalla programmet wcnt.


Skriva filer.

 

Vill man skriva i en fil måste man öppna den med ‘w’ eller ‘a’ enligt ovan. Själva skrivningen kan man göra med funktionen fputc(). Funktionen vill ha en variabel av typen char eller int som första parameter, och pekaren till FILE-variabeln som andra parameter, t.ex:

 

FILE *utfil;

if ((utfil = fopen(”c:\\katalog\\filnamn.txt”,”w”)) == 0)

{

   puts(”Det gick inte att öppna filen!”);

}

else

{

   .

   . // Skriv i filen.

   .

}

 

Vill man t.ex. kopiera innehållet i en fil till en annan, tecken för tecken, kan man klara det med en enda liten loop:

 

while((cBokstav = fgetc(infil)) != EOF) fputc(cBokstav, utfil);

 

Övningsuppgift:

·      Skriv ett program filecopy som kopierar filer enligt ovanstående.

·      Först ska det fråga efter infilens namn, sedan efter utfilens.

·      Därefter sker kopiering.

·      Antal kopierade tecken ska slutligen redovisas.

·      Efter avslutad körning bör QuickWin-fönstret se ut så här:

 

                

 


Man kan också använda fputs() och fgets() för att läsa skriva/läsa hela rader. Syntax:

 

fputs(<textpekare>, < FILE-variabel>);

fgets(<textpekare>, <charlistans längd - 1>, < FILE-variabel>);

 

Fgets() kopierar en rad från filen och lägger in där textpekaren pekar. Om raden är längre än maxlängd kommer läsningen att avbrytas där. Resten av raden läses i så fall vid nästa fgets(). Om filen är slut kommer fgets att returnera värdet noll, vilket man alltså bör testa vid varje läsning.

 

Överkursuppgift:

·      Skriv ett program compare som jämför två filer.

·      Använd radvis läsning.

·      Jämför raderna med funktionen strcmp().

·      Skriv en resultatfil som innehåller de rader som skiljer sig: första filens rad, andra filens rad och en blankrad (så att men ser vilka rader som hör ihop).

·      Skapa textfiler att testa med. Kontrollera resultatet i resultatfilen.


Positionering.

 

Som nämnts tidigare har vi en filpekare som håller reda på var i filen vi läser/skriver, och den kan stå antingen i början eller i slutet av filen när vi öppnar den. Ibland kan det dock vara av intresse att snabbt ändra filpekaren till att läsa eller skriva på ett annat ställe i filen. Detta kan göras med funktionen fseek():

 

fseek(<FILE-variabel>, <avstånd>, <referenspunkt>)

 

< FILE-variabel> är den vanliga pekaren till din variabel av typen FILE, vilken fick sitt värde när du gjorde fopen().

 

<avstånd> är av typen long, och anger hur många bytes vi ska flytta filpekaren. Vill vi backa i filen är det tillåtet att använda negativa värden.

 

<referenspunkt> ska vara ett av tre värden, och talar om från vilken position avståndet räknas. (Man behöver alltså inte räkna från aktuell position):

 

                   0      =     räkna från filens början.
                   1      =     räkna från aktuell position.
                   2      =     räkna från filens slut.

 

Funktionen returnerar ett värde av typen int. Om man anger ett avstånd som i kombination med referenspunkten gör att man hamnar utanför filen, kommer returvärdet att vara -1, annars 0. Man bör därför använda fseek() i en test av något slag, t.ex:

 

if(fseek(infil, avstand, 2) == 0)

{

         // Läs...

}

 

Överkursuppgift:

·      Skriv ett program krypto som krypterar en fil.

·      Metoden ska vara den att varannat tecken ska läsas från slutet av infilen och varannat från början. Detta låter sig göras endast om filen innehåller ett jämnt antal tecken. om så inte är fallet lägger man  tíll en blank i slutet av filen.

·      Programmet skapar en ny fil.

·      Användaren ska ange infil och utfil.

 

Överkursuppgift:

·      Skriv ett program dekrypto som återställer en fil som skapats av krypto.

·      Programmet skapar en ny fil.

·      Användaren ska ange infil och utfil.